package com.atguigu.yygh.host.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.yygh.common.config.YyghException;
import com.atguigu.yygh.host.repository.ScheduleRepository;
import com.atguigu.yygh.host.service.DepartmentService;
import com.atguigu.yygh.host.service.HospitalService;
import com.atguigu.yygh.host.service.ScheduleService;
import com.atguigu.yygh.model.hosp.BookingRule;
import com.atguigu.yygh.model.hosp.Department;
import com.atguigu.yygh.model.hosp.Hospital;
import com.atguigu.yygh.model.hosp.Schedule;
import com.atguigu.yygh.vo.hosp.BookingScheduleRuleVo;
import com.atguigu.yygh.vo.hosp.ScheduleOrderVo;
import com.atguigu.yygh.vo.hosp.ScheduleQueryVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author DongYu Zhou
 * @date 2022/6/24 21:50
 */
@Service
public class ScheduleServiceImpl implements ScheduleService {

    @Autowired
    private ScheduleRepository scheduleRepository;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private HospitalService hospitalService;
    @Autowired
    private DepartmentService departmentService;


    // 保存 + 修改
    @Override
    public void save(Map<String, Object> paramMap) {
        //1转化对象
        String paramJSONString = JSONObject.toJSONString(paramMap);
        Schedule schedule = JSONObject.parseObject(paramJSONString, Schedule.class);
        //2查询排班信息
        Schedule targetSchedule =
                scheduleRepository.getByHoscodeAndHosScheduleId(
                        schedule.getHoscode(), schedule.getHosScheduleId());
        if (targetSchedule != null) {
            //3有,修改
            schedule.setId(targetSchedule.getId());
            schedule.setUpdateTime(new Date());
            scheduleRepository.save(schedule);

        } else {
            //4没有，新增
            schedule.setCreateTime(new Date());
            schedule.setUpdateTime(new Date());
            schedule.setStatus(1);
            schedule.setIsDeleted(0);
            scheduleRepository.save(schedule);
        }
    }

    //分页查询
    @Override
    public Page<Schedule> selectPage(int page, int limit, ScheduleQueryVo scheduleQueryVo) {
        Sort sort = Sort.by(Sort.Direction.DESC, "createTime");

        Schedule schedule = new Schedule();
        BeanUtils.copyProperties(scheduleQueryVo, schedule);

        Page<Schedule> pageModel = scheduleRepository.findAll(
                Example.of(schedule, ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING).withIgnoreCase(true)),
                PageRequest.of((page - 1), limit, sort));
        return pageModel;
    }

    //删除
    @Override
    public void remove(String hosScheduleId) {
        Schedule schedule = scheduleRepository.getByHosScheduleId(hosScheduleId);
        if (schedule == null) {
            throw new YyghException(20001, "错误");
        }
        scheduleRepository.deleteById(schedule.getId());
    }

    /**
     * *如果是mysql如何实现
     * <p>
     * #带条件、带分页，根据排班日期统计号源信息
     * SELECT s.workDate,COUNT(s.id) AS docCount,
     * SUM(s.reservedNumber) AS reservedNumber,
     * SUM(s.availableNumber) AS availableNumber
     * FROM SCHEDULE s
     * WHERE s.hoscode=hoscode AND s.depcode=depcode
     * GROUP BY s.workDate
     * ORDER BY  s.workDate ASC
     * LIMIT XXXX;
     * <p>
     * *接口方法实现
     *
     * @param page
     * @param limit
     * @param hoscode
     * @param depcode
     * @return
     */
    @Override
    public Map<String, Object> getScheduleRule(Long page, Long limit, String hoscode, String depcode) {
        //1. 定义最终返回对象
        HashMap<String, Object> result = new HashMap<>();

        //2. 带条件分页聚合查询(list)
        //2.1 创建查询对象
        Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode);
        //2.2 创建聚合查询对象
        Aggregation agg = Aggregation.newAggregation(
                // 筛选条件
                Aggregation.match(criteria),
                // 设置分组聚合信息 这个有点多
                Aggregation.group("workDate").
                        // 设置聚合返回结果
                                first("workDate").as("workDate").
                        count().as("docCount").
                        sum("reservedNumber").as("reservedNumber").
                        sum("availableNumber").as("availableNumber"),
                // 排序
                Aggregation.sort(Sort.Direction.ASC, "workDate"),
                // 分页
                Aggregation.skip((page - 1) * limit),
                Aggregation.limit(limit)
        );
        //2.3 进行聚合查询
        List<BookingScheduleRuleVo> bookingScheduleRuleVoList = mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class).getMappedResults();

        //3. 带条件的聚合查询(tatal)
        Aggregation aggTotal = Aggregation.newAggregation(
                Aggregation.match(criteria), Aggregation.group("workDate")
        );

        //3.2进行聚合查询
        AggregationResults<BookingScheduleRuleVo> aggregateTotal = mongoTemplate.aggregate(aggTotal, Schedule.class, BookingScheduleRuleVo.class);
        List<BookingScheduleRuleVo> totalList = aggregateTotal.getMappedResults();
        //3.3获取长度
        int total = totalList.size();
        //4进行日期转化成周几（借助工具）
//        for (BookingScheduleRuleVo bookingScheduleRuleVo : bookingScheduleRuleVoList) {
//            Date workDate = bookingScheduleRuleVo.getWorkDate();
//            String dayOfWeek = this.getDayOfWeek(new DateTime(workDate));
//            bookingScheduleRuleVo.setDayOfWeek(dayOfWeek);
//        }
        bookingScheduleRuleVoList.stream().forEach(i -> {
            Date workDate = i.getWorkDate();
            String dayOfWeek = this.getDayOfWeek(new DateTime(workDate));
            i.setDayOfWeek(dayOfWeek);
        });
        //5封装数据
        result.put("bookingScheduleRuleList", bookingScheduleRuleVoList);
        result.put("total", total);
        //6补全信息
        //获取医院名称
        String hosName = hospitalService.getHospName(hoscode);
        //其他基础数据
        Map<String, String> baseMap = new HashMap<>();
        baseMap.put("hosname", hosName);
        result.put("baseMap", baseMap);
        return result;
    }

    //根据医院编号 、科室编号和工作日期，查询排班详细信息
    @Override
    public List<Schedule> getScheduleDetail(String hoscode, String depcode, String workDate) {
        List<Schedule> list =
                scheduleRepository.getByHoscodeAndDepcodeAndWorkDate(
                        hoscode, depcode, new DateTime(workDate).toDate()
                );
        list.stream().forEach(item -> {
            this.packageSchedule(item);
        });
        return list;
    }

    //获取可预约排班统计数据
    @Override
    public Map<String, Object> getBookingchedule(Integer page, Integer limit, String hoscode, String depcode) {
        //1、根据hoscode查询医院信息，取出预约规则
        Hospital hospital = hospitalService.hospitalShow(hoscode);
        if (hospital == null) {
            throw new YyghException(20001, "医院信息有误");
        }
        BookingRule bookingRule = hospital.getBookingRule();
        //2、根据预约规则、分页参数查询可以预约的日期集合分页对象（IPage<Date>）
        IPage<Date> iPage = this.getDateListPage(page, limit, bookingRule);
        List<Date> dateList = iPage.getRecords();
        //3、参考后台接口、实现聚合查询排班信息
        //3.1创建查询条件对象
        Criteria criteria = Criteria.where("hoscode").is(hoscode)
                .and("depcode").is(depcode).and("workDate").in(dateList);
        //3.2创建聚合查询对象
        Aggregation agg = Aggregation.newAggregation(
                //3.2.1设置筛选条件
                Aggregation.match(criteria),
                //3.2.2设置分组聚合信息
                Aggregation.group("workDate")
                        //3.2.3设置聚合返回结果
                        .first("workDate").as("workDate")
                        .count().as("docCount")
                        .sum("reservedNumber").as("reservedNumber")
                        .sum("availableNumber").as("availableNumber")
        );
        //3.3聚合查询
        AggregationResults<BookingScheduleRuleVo> aggregate =
                mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);
        List<BookingScheduleRuleVo> scheduleVoList = aggregate.getMappedResults();
        //3.4集合类型转化，scheduleVoList => map k:workDate v:BookingScheduleRuleVo
        Map<Date,BookingScheduleRuleVo> scheduleVoMap = new HashMap<>();
        if(!CollectionUtils.isEmpty(scheduleVoList)){
            scheduleVoMap = scheduleVoList.stream().collect(Collectors.toMap(
                    BookingScheduleRuleVo::getWorkDate,
                    BookingScheduleRuleVo->BookingScheduleRuleVo
            ));
        }

        //4、把步骤2、3数据整合
        //4、把步骤2、3数据整合(1)dateList  (2)scheduleVoMap
//4.1 创建合并后的数据集合
        List<BookingScheduleRuleVo> bookingScheduleRuleVoList = new ArrayList<>();
//4.2遍历dateList，取出每一天日期date
        for (int i = 0,let = dateList.size(); i < let; i++) {
            Date date = dateList.get(i);
            //4.3拿date从scheduleVoMap取出对应排班统计数据
            BookingScheduleRuleVo bookingScheduleRuleVo = scheduleVoMap.get(date);
            //4.4如果对应排班统计数据为空，初始化排班统计数据
            if(bookingScheduleRuleVo==null){
                bookingScheduleRuleVo = new BookingScheduleRuleVo();
                bookingScheduleRuleVo.setDocCount(0);
                bookingScheduleRuleVo.setAvailableNumber(-1);
            }
            //4.5设置排班日期
            bookingScheduleRuleVo.setWorkDate(date);
            bookingScheduleRuleVo.setWorkDateMd(date);
            //4.6根据date换算周几
            String dayOfWeek = this.getDayOfWeek(new DateTime(date));
            bookingScheduleRuleVo.setDayOfWeek(dayOfWeek);
            //4.7根据时间判断统计数据状态值
            //状态 0：正常 1：即将放号 -1：当天已停止挂号
            //最后一页，最后一条记录，状态1：即将放号
            if(i==let-1&&page==iPage.getPages()){
                bookingScheduleRuleVo.setStatus(1);
            }else{
                bookingScheduleRuleVo.setStatus(0);
            }

            //第一页，第一条记录，判断是否过停止挂号时间(日期+时间)
            if(i==0&&page==1){
                DateTime stopDateTime = this.getDateTime(
                        new Date(),bookingRule.getStopTime());
                if(stopDateTime.isBeforeNow()){
                    //状态-1：当天已停止挂号
                    bookingScheduleRuleVo.setStatus(-1);
                }

            }
            //4.8把对象存入合并后的数据集合
            bookingScheduleRuleVoList.add(bookingScheduleRuleVo);
        }

        //5、封装数据、返回
        Map<String,Object> result = new HashMap<>();
        //可预约日期规则数据
        result.put("bookingScheduleList", bookingScheduleRuleVoList);
        result.put("total", iPage.getTotal());
        //其他基础数据
        Map<String, String> baseMap = new HashMap<>();
        //医院名称
        baseMap.put("hosname", hospitalService.getHospName(hoscode));
        //科室
        Department department =departmentService.getDepartment(hoscode, depcode);
        //大科室名称
        baseMap.put("bigname", department.getBigname());
        //科室名称
        baseMap.put("depname", department.getDepname());
        //月
        baseMap.put("workDateString", new DateTime().toString("yyyy年MM月"));
        //放号时间
        baseMap.put("releaseTime", bookingRule.getReleaseTime());
        //停号时间
        baseMap.put("stopTime", bookingRule.getStopTime());
        result.put("baseMap", baseMap);
        return result;
    }

    @Override
    public Schedule findScheduleById(String id) {
        Schedule schedule = this.packageSchedule(scheduleRepository.findById(id).get());
        return schedule;
    }

    @Override
    public ScheduleOrderVo getScheduleOrderVo(String scheduleId) {
        //1根据scheduleId查询排班信息
        Schedule schedule = scheduleRepository.findById(scheduleId).get();
        if(schedule==null){
            throw new YyghException(20001,"排班信息有误");
        }
        //2根据排班信息hoscode，查询医院信息
        Hospital hospital = hospitalService.hospitalShow(
                schedule.getHoscode());
        if(hospital==null){
            throw new YyghException(20001,"医院信息有误");
        }
        //3获取规则信息
        BookingRule bookingRule = hospital.getBookingRule();
        if(bookingRule==null){
            throw new YyghException(20001,"规则信息有误");
        }
        //4封装基础数据
        ScheduleOrderVo scheduleOrderVo = new ScheduleOrderVo();
        scheduleOrderVo.setHoscode(hospital.getHoscode());
        scheduleOrderVo.setHosname(hospital.getHosname());
        scheduleOrderVo.setDepcode(schedule.getDepcode());
        scheduleOrderVo.setDepname(departmentService.getDepartment(schedule.getHoscode(), schedule.getDepcode()).getDepname());
        scheduleOrderVo.setHosScheduleId(schedule.getHosScheduleId());
        scheduleOrderVo.setAvailableNumber(schedule.getAvailableNumber());
        scheduleOrderVo.setTitle(schedule.getTitle());
        scheduleOrderVo.setReserveDate(schedule.getWorkDate());
        scheduleOrderVo.setReserveTime(schedule.getWorkTime());
        scheduleOrderVo.setAmount(schedule.getAmount());
        //5封装根据规则计算出的时间数据
        //5.1退号时间（Date）（退号截止天+退号截止时间点）
        DateTime quitDate = new DateTime(schedule.getWorkDate())
                .plusDays(bookingRule.getQuitDay());
        DateTime quitDateTime = this.getDateTime(quitDate.toDate(),
                bookingRule.getQuitTime() );
        scheduleOrderVo.setQuitTime(quitDateTime.toDate());
        //预约开始时间
        DateTime startTime = this.getDateTime(new Date(), bookingRule.getReleaseTime());
        scheduleOrderVo.setStartTime(startTime.toDate());

        //预约截止时间
        DateTime endTime = this.getDateTime(
                new DateTime().plusDays(bookingRule.getCycle()).toDate(),
                bookingRule.getStopTime());
        scheduleOrderVo.setEndTime(endTime.toDate());

        //当天停止挂号时间
        DateTime stopTime = this.getDateTime(new Date(), bookingRule.getStopTime());
        scheduleOrderVo.setStopTime(stopTime.toDate());
        return scheduleOrderVo;
    }

    /**
     * 修改排班
     * @param schedule
     */
    @Override
    public void update(Schedule schedule) {
        scheduleRepository.save(schedule);
    }

    //查询排班信息
    @Override
    public Schedule getScheduleByInfo(String hoscode, String scheduleId) {
        return scheduleRepository.getByHoscodeAndHosScheduleId(hoscode,scheduleId);
    }

    private IPage<Date> getDateListPage(Integer page, Integer limit, BookingRule bookingRule) {
        //1从规则中获取开始挂号时间（当前系统日期+放号时间）
        DateTime releaseDateTime = this.getDateTime(
                new Date(), bookingRule.getReleaseTime());
        //2从规则中获取预约周期，判断如果过了当天放号时间，周期+1
        Integer cycle = bookingRule.getCycle();
        if (releaseDateTime.isBeforeNow()) cycle += 1;
        //3根据周期推算出可以挂号日期集合List<Date>
        List<Date> dateList = new ArrayList<>();
        for (int i = 0; i < cycle; i++) {
            DateTime plusDays = new DateTime().plusDays(i);
            String plusDaysStr = plusDays.toString("yyyy-MM-dd");
            dateList.add(new DateTime(plusDaysStr).toDate());
        }
        //4准备分页参数
        int start = (page - 1) * limit;
        int end = (page - 1) * limit + limit;
        if (end > dateList.size()) end = dateList.size();

        //5获取分页的日期集合List<Date>
        List<Date> datePageList = new ArrayList<>();
        for (int i = start; i < end; i++) {
            datePageList.add(dateList.get(i));
        }

        //6把数据封装到ipage里，返回
        //IPage<Date> iPage = new Page<Date>(page, limit, dateList.size());
        IPage<Date> iPage=  new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page, limit, dateList.size());
        iPage.setRecords(datePageList);
        return iPage;
    }

    /**
     * 将Date日期（yyyy-MM-dd HH:mm）转换为DateTime
     */
    private DateTime getDateTime(Date date, String timeString) {
        String dateTimeString = new DateTime(date)
                .toString("yyyy-MM-dd") + " " + timeString;
        DateTime dateTime = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm")
                .parseDateTime(dateTimeString);
        return dateTime;

    }

    //封装排班详情其他值 医院名称、科室名称、日期对应星期
    private Schedule packageSchedule(Schedule schedule) {
        //设置医院名称

        schedule.getParam().put("hosname", hospitalService.getHospName(schedule.getHoscode()));
        //设置科室名称
        schedule.getParam().put("depname",
                departmentService.getDepName(schedule.getHoscode(), schedule.getDepcode()));
        //设置日期对应星期
        schedule.getParam().put("dayOfWeek", this.getDayOfWeek(new DateTime(schedule.getWorkDate())));
        return schedule;
    }

    /**
     * 根据日期获取周几数据
     *
     * @param dateTime
     * @return
     */
    private String getDayOfWeek(DateTime dateTime) {
        String dayOfWeek = "";
        switch (dateTime.getDayOfWeek()) {
            case DateTimeConstants.SUNDAY:
                dayOfWeek = "周日";
                break;
            case DateTimeConstants.MONDAY:
                dayOfWeek = "周一";
                break;
            case DateTimeConstants.TUESDAY:
                dayOfWeek = "周二";
                break;
            case DateTimeConstants.WEDNESDAY:
                dayOfWeek = "周三";
                break;
            case DateTimeConstants.THURSDAY:
                dayOfWeek = "周四";
                break;
            case DateTimeConstants.FRIDAY:
                dayOfWeek = "周五";
                break;
            case DateTimeConstants.SATURDAY:
                dayOfWeek = "周六";
            default:
                break;
        }
        return dayOfWeek;
    }


}




































